#include "MCWin32.h" // should be first include.

#include "MCMessageHeader.h"

#include "MCDefines.h"
#include "MCAddress.h"
#include "MCIterator.h"
#include "MCLibetpan.h"
#include "MCLock.h"

#include <string.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <libetpan/libetpan.h>

using namespace mailcore;

static struct mailimf_address_list * lep_address_list_from_array(Array * addresses);
static struct mailimf_mailbox_list * lep_mailbox_list_from_array(Array * addresses);
static Array * lep_address_list_from_lep_addr(struct mailimf_address_list * addr_list);
static Array * lep_address_list_from_lep_mailbox(struct mailimf_mailbox_list * mb_list);

static Array * msg_id_to_string_array(clist * msgids);
static clist * msg_id_from_string_array(Array * msgids);

#define MAX_HOSTNAME 512

MessageHeader::MessageHeader()
{
    init(true, true);
}

MessageHeader::MessageHeader(MessageHeader * other)
{
    init(false, other->mMessageID == NULL);
    if (other->mMessageID != NULL) {
        setMessageID(other->mMessageID);
        mMessageIDAutoGenerated = other->isMessageIDAutoGenerated();
    }
    setReferences(other->mReferences);
    setInReplyTo(other->mInReplyTo);
    setSender(other->mSender);
    setFrom(other->mFrom);
    setTo(other->mTo);
    setCc(other->mCc);
    setBcc(other->mBcc);
    setReplyTo(other->mReplyTo);
    setSubject(other->mSubject);
    setDate(other->date());
    setReceivedDate(other->receivedDate());
    setExtraHeaders(other->mExtraHeaders);
}

void MessageHeader::init(bool generateDate, bool generateMessageID)
{
    mMessageID = NULL;
    mMessageIDAutoGenerated = false;
    mReferences = NULL;
    mInReplyTo = NULL;
    mSender = NULL;
    mFrom = NULL;
    mTo = NULL;
    mCc = NULL;
    mBcc = NULL;
    mReplyTo = NULL;
    mSubject = NULL;
    mDate = (time_t) -1;
    mReceivedDate = (time_t) -1;
    mExtraHeaders = NULL;
    
    if (generateDate) {
        time_t date;
        date = time(NULL);
        setDate(date);
        setReceivedDate(date);
    }
    if (generateMessageID) {
        static String * hostname = NULL;
        static MC_LOCK_TYPE lock = MC_LOCK_INITIAL_VALUE;
        
        MC_LOCK(&lock);
        if (hostname == NULL) {
            char name[MAX_HOSTNAME];
            int r;

            r = gethostname(name, MAX_HOSTNAME);
            if (r < 0) {
                hostname = NULL;
            }
            else {
                hostname = new String(name);
            }
            if (hostname == NULL) {
                hostname = new String("localhost");
            }
        }
        MC_UNLOCK(&lock);
        
        String * messageID = new String();
        messageID->appendString(String::uuidString());
        messageID->appendUTF8Characters("@");
        messageID->appendString(hostname);
        setMessageID(messageID);
        messageID->release();
        
        mMessageIDAutoGenerated = true;
    }
}

MessageHeader::~MessageHeader()
{
    MC_SAFE_RELEASE(mMessageID);
    MC_SAFE_RELEASE(mReferences);
    MC_SAFE_RELEASE(mInReplyTo);
    MC_SAFE_RELEASE(mSender);
    MC_SAFE_RELEASE(mFrom);
    MC_SAFE_RELEASE(mTo);
    MC_SAFE_RELEASE(mCc);
    MC_SAFE_RELEASE(mBcc);
    MC_SAFE_RELEASE(mReplyTo);
    MC_SAFE_RELEASE(mSubject);
    MC_SAFE_RELEASE(mExtraHeaders);
}

String * MessageHeader::description()
{
    String * result = String::string();
    result->appendUTF8Format("<%s:%p\n", className()->UTF8Characters(), this);
    if (mMessageID != NULL) {
        result->appendUTF8Format("Message-ID: %s\n", mMessageID->UTF8Characters());
    }
    if (mReferences != NULL) {
        result->appendUTF8Format("References: %s\n", mReferences->description()->UTF8Characters());
    }
    if (mInReplyTo != NULL) {
        result->appendUTF8Format("In-Reply-To: %s\n", mInReplyTo->description()->UTF8Characters());
    }
    if (mSender != NULL) {
        result->appendUTF8Format("Sender: %s\n", mSender->description()->UTF8Characters());
    }
    if (mFrom != NULL) {
        result->appendUTF8Format("From: %s\n", mFrom->description()->UTF8Characters());
    }
    if (mTo != NULL) {
        result->appendUTF8Format("To: %s\n", mTo->description()->UTF8Characters());
    }
    if (mCc != NULL) {
        result->appendUTF8Format("Cc: %s\n", mCc->description()->UTF8Characters());
    }
    if (mBcc != NULL) {
        result->appendUTF8Format("Bcc: %s\n", mBcc->description()->UTF8Characters());
    }
    if (mReplyTo != NULL) {
        result->appendUTF8Format("Reply-To: %s\n", mReplyTo->description()->UTF8Characters());
    }
    if (mSubject != NULL) {
        result->appendUTF8Format("Subject: %s\n", mSubject->UTF8Characters());
    }
    if (mExtraHeaders != NULL) {
        mc_foreachhashmapKeyAndValue(String, header, String, value, mExtraHeaders) {
            result->appendUTF8Format("%s: %s\n", header->UTF8Characters(), value->UTF8Characters());
        }
    }
    result->appendUTF8Format(">");
    
    return result;
}

Object * MessageHeader::copy()
{
    return new MessageHeader(this);
}

void MessageHeader::setMessageID(String * messageID)
{
    MC_SAFE_REPLACE_COPY(String, mMessageID, messageID);
    mMessageIDAutoGenerated = false;
}

String * MessageHeader::messageID()
{
    return mMessageID;
}

bool MessageHeader::isMessageIDAutoGenerated()
{
    return mMessageIDAutoGenerated;
}

void MessageHeader::setReferences(Array * references)
{
    MC_SAFE_REPLACE_COPY(Array, mReferences, references);
}

Array * MessageHeader::references()
{
    return mReferences;
}

void MessageHeader::setInReplyTo(Array * inReplyTo)
{
    MC_SAFE_REPLACE_COPY(Array, mInReplyTo, inReplyTo);
}

Array * MessageHeader::inReplyTo()
{
    return mInReplyTo;
}

void MessageHeader::setDate(time_t date)
{
    mDate = date;
}

time_t MessageHeader::date()
{
    return mDate;
}

void MessageHeader::setReceivedDate(time_t date)
{
    mReceivedDate = date;
}

time_t MessageHeader::receivedDate()
{
    return mReceivedDate;
}

void MessageHeader::setSender(Address * sender)
{
    MC_SAFE_REPLACE_RETAIN(Address, mSender, sender);
}

Address * MessageHeader::sender()
{
    return mSender;
}

void MessageHeader::setFrom(Address * from)
{
    MC_SAFE_REPLACE_RETAIN(Address, mFrom, from);
}

Address * MessageHeader::from()
{
    return mFrom;
}

void MessageHeader::setTo(Array * to)
{
    MC_SAFE_REPLACE_COPY(Array, mTo, to);
}

Array * MessageHeader::to()
{
    return mTo;
}

void MessageHeader::setCc(Array * cc)
{
    MC_SAFE_REPLACE_COPY(Array, mCc, cc);
}

Array * MessageHeader::cc()
{
    return mCc;
}

void MessageHeader::setBcc(Array * bcc)
{
    MC_SAFE_REPLACE_COPY(Array, mBcc, bcc);
}

Array * MessageHeader::bcc()
{
    return mBcc;
}

void MessageHeader::setReplyTo(Array * replyTo)
{
    MC_SAFE_REPLACE_COPY(Array, mReplyTo, replyTo);
}

Array * MessageHeader::replyTo()
{
    return mReplyTo;
}

void MessageHeader::setSubject(String * subject)
{
    MC_SAFE_REPLACE_COPY(String, mSubject, subject);
}

String * MessageHeader::subject()
{
    return mSubject;
}

void MessageHeader::setUserAgent(String * userAgent)
{
    setExtraHeader(MCSTR("X-Mailer"), userAgent);
}

String * MessageHeader::userAgent()
{
    return extraHeaderValueForName(MCSTR("X-Mailer"));
}

void MessageHeader::setExtraHeaders(HashMap * headers)
{
    MC_SAFE_REPLACE_COPY(HashMap, mExtraHeaders, headers);
}

Array * MessageHeader::allExtraHeadersNames()
{
    if (mExtraHeaders == NULL)
        return Array::array();
    return mExtraHeaders->allKeys();
}

void MessageHeader::setExtraHeader(String * name, String * object)
{
    if (mExtraHeaders == NULL) {
        mExtraHeaders = new HashMap();
    }
    removeExtraHeader(name);
    if (object == NULL) {
        return;
    }
    mExtraHeaders->setObjectForKey(name, object);
}

void MessageHeader::removeExtraHeader(String * name)
{
    if (mExtraHeaders == NULL)
        return;
    mc_foreachhashmapKey(String, key, mExtraHeaders) {
        if (key->isEqualCaseInsensitive(name)) {
            mExtraHeaders->removeObjectForKey(key);
            break;
        }
    }
}

String * MessageHeader::extraHeaderValueForName(String * name)
{
    String * result = NULL;
    mc_foreachhashmapKey(String, key, mExtraHeaders) {
        if (key->isEqualCaseInsensitive(name)) {
            result = (String *) mExtraHeaders->objectForKey(key);
        }
    }
    return result;
}

String * MessageHeader::extractedSubject()
{
    if (subject() == NULL)
        return NULL;
    return subject()->extractedSubject();
}

String * MessageHeader::partialExtractedSubject()
{
    if (subject() == NULL)
        return NULL;
    return subject()->extractedSubjectAndKeepBracket(true);
}

void MessageHeader::importHeadersData(Data * data)
{
    size_t cur_token;
    struct mailimf_fields * fields;
    int r;
    
    cur_token = 0;
    r = mailimf_envelope_and_optional_fields_parse(data->bytes(), data->length(), &cur_token, &fields);
    if (r != MAILIMF_NO_ERROR) {
        return;
    }
    
    importIMFFields(fields);

    mailimf_fields_free(fields);
}

void MessageHeader::importIMFFields(struct mailimf_fields * fields)
{
    struct mailimf_single_fields single_fields;
    
    mailimf_single_fields_init(&single_fields, fields);
    
    /* date */
    
    if (single_fields.fld_orig_date != NULL) {
        time_t timestamp;
        timestamp = timestampFromDate(single_fields.fld_orig_date->dt_date_time);
        setDate(timestamp);
        setReceivedDate(timestamp);
        //MCLog("%lu %lu", (unsigned long) timestamp, date());
    }
    
    /* subject */
    if (single_fields.fld_subject != NULL) {
        char * subject;
        
        subject = single_fields.fld_subject->sbj_value;
        setSubject(String::stringByDecodingMIMEHeaderValue(subject));
    }
    
    /* sender */
    if (single_fields.fld_sender != NULL) {
        struct mailimf_mailbox * mb;
        Address * address;
        
        mb = single_fields.fld_sender->snd_mb;
        if (mb != NULL) {
            address = Address::addressWithIMFMailbox(mb);
            setSender(address);
        }
    }
    
    /* from */
    if (single_fields.fld_from != NULL) {
        struct mailimf_mailbox_list * mb_list;
        Array * addresses;
        
        mb_list = single_fields.fld_from->frm_mb_list;
        addresses = lep_address_list_from_lep_mailbox(mb_list);
        if (addresses->count() > 0) {
            setFrom((Address *) (addresses->objectAtIndex(0)));
        }
    }
    
    /* replyto */
    if (single_fields.fld_reply_to != NULL) {
        struct mailimf_address_list * addr_list;
        Array * addresses;
        
        addr_list = single_fields.fld_reply_to->rt_addr_list;
        addresses = lep_address_list_from_lep_addr(addr_list);
        setReplyTo(addresses);
    }
    
    /* to */
    if (single_fields.fld_to != NULL) {
        struct mailimf_address_list * addr_list;
        Array * addresses;
        
        addr_list = single_fields.fld_to->to_addr_list;
        addresses = lep_address_list_from_lep_addr(addr_list);
        setTo(addresses);
    }
    
    /* cc */
    if (single_fields.fld_cc != NULL) {
        struct mailimf_address_list * addr_list;
        Array * addresses;
        
        addr_list = single_fields.fld_cc->cc_addr_list;
        addresses = lep_address_list_from_lep_addr(addr_list);
        setCc(addresses);
    }
    
    /* bcc */
    if (single_fields.fld_bcc != NULL) {
        struct mailimf_address_list * addr_list;
        Array * addresses;
        
        addr_list = single_fields.fld_bcc->bcc_addr_list;
        addresses = lep_address_list_from_lep_addr(addr_list);
        setBcc(addresses);
    }
    
    /* msgid */
    if (single_fields.fld_message_id != NULL) {
        char * msgid;
        String * str;
        
        msgid = single_fields.fld_message_id->mid_value;
        str = String::stringWithUTF8Characters(msgid);
        setMessageID(str);
    }
    
    /* references */
    if (single_fields.fld_references != NULL) {
        clist * msg_id_list;
        Array * msgids;
        
        msg_id_list = single_fields.fld_references->mid_list;
        msgids = msg_id_to_string_array(msg_id_list);
        setReferences(msgids);
    }
    
    /* inreplyto */
    if (single_fields.fld_in_reply_to != NULL) {
        clist * msg_id_list;
        Array * msgids;
        
        msg_id_list = single_fields.fld_in_reply_to->mid_list;
        msgids = msg_id_to_string_array(msg_id_list);
        setInReplyTo(msgids);
    }
    
    // Take care of other headers.
    for(clistiter * cur = clist_begin(fields->fld_list) ; cur != NULL ; cur = clist_next(cur)) {
        struct mailimf_field * field;

        field = (mailimf_field *)clist_content(cur);

        if (field->fld_type != MAILIMF_FIELD_OPTIONAL_FIELD) {
            continue;
        }
        
        char * fieldName;
        String * fieldNameStr;
        
        fieldName = field->fld_data.fld_optional_field->fld_name;
        fieldNameStr = String::stringWithUTF8Characters(fieldName);
        if (fieldNameStr == NULL) {
            continue;
        }

        // Set only if this optional-field is not set
        if (extraHeaderValueForName(fieldNameStr) == NULL) {
            char * fieldValue;
            String * fieldValueStr;
            
            fieldValue = field->fld_data.fld_optional_field->fld_value;
            fieldValueStr = String::stringByDecodingMIMEHeaderValue(fieldValue);
            if (fieldValueStr != NULL) {
                setExtraHeader(fieldNameStr, fieldValueStr);
            }
        }
    }
}

#pragma mark RFC 2822 mailbox conversion

static Array * lep_address_list_from_lep_mailbox(struct mailimf_mailbox_list * mb_list)
{
    Array * result;
    clistiter * cur;
    
    result = Array::array();
    for(cur = clist_begin(mb_list->mb_list) ; cur != NULL ; cur = clist_next(cur)) {
        struct mailimf_mailbox * mb;
        Address * address;
        
        mb = (struct mailimf_mailbox *) clist_content(cur);
        address = Address::addressWithIMFMailbox(mb);
        result->addObject(address);
    }
    
    return result;
}

static Array * lep_address_list_from_lep_addr(struct mailimf_address_list * addr_list)
{
    Array * result;
    clistiter * cur;
    
    result = Array::array();
    
    if (addr_list == NULL) {
        return result;
    }
    
    if (addr_list->ad_list == NULL) {
        return result;
    }
    
    for(cur = clist_begin(addr_list->ad_list) ; cur != NULL ;
        cur = clist_next(cur)) {
        struct mailimf_address * addr;
        
        addr = (struct mailimf_address *) clist_content(cur);
        switch (addr->ad_type) {
            case MAILIMF_ADDRESS_MAILBOX:
            {
                Address * address;
                
                address = Address::addressWithIMFMailbox(addr->ad_data.ad_mailbox);
                result->addObject(address);
                break;
            }
            
            case MAILIMF_ADDRESS_GROUP:
            {
                if (addr->ad_data.ad_group->grp_mb_list != NULL) {
                    Array * subArray;
                    
                    subArray = lep_address_list_from_lep_mailbox(addr->ad_data.ad_group->grp_mb_list);
                    result->addObjectsFromArray(subArray);
                }
                break;
            }
        }
    }
    
    return result;
}

static struct mailimf_mailbox_list * lep_mailbox_list_from_array(Array * addresses)
{
    struct mailimf_mailbox_list * mb_list;
    
    if (addresses == NULL)
        return NULL;
    
    if (addresses->count() == 0)
        return NULL;
    
    mb_list = mailimf_mailbox_list_new_empty();
    
    for(unsigned i = 0 ; i < addresses->count() ; i ++) {
        Address * address = (Address *) addresses->objectAtIndex(i);
        struct mailimf_mailbox * mailbox = address->createIMFMailbox();
        mailimf_mailbox_list_add(mb_list, mailbox);
    }
    
    return mb_list;
}

static struct mailimf_address_list * lep_address_list_from_array(Array * addresses)
{
    struct mailimf_address_list * addr_list;
    
    if (addresses == NULL)
        return NULL;
    
    if (addresses->count() == 0)
        return NULL;
    
    addr_list = mailimf_address_list_new_empty();

    for(unsigned i = 0 ; i < addresses->count() ; i ++) {
        Address * address = (Address *) addresses->objectAtIndex(i);
        struct mailimf_address * addr = address->createIMFAddress();
        mailimf_address_list_add(addr_list, addr);
    }
    
    return addr_list;
}

#pragma mark Message-ID conversion

static Array * msg_id_to_string_array(clist * msgids)
{
    clistiter * cur;
    Array * result;
    
    result = Array::array();
    
    for(cur = clist_begin(msgids) ; cur != NULL ; cur = clist_next(cur)) {
        char * msgid;
        String * str;
        
        msgid = (char *) clist_content(cur);
        str = String::stringWithUTF8Characters(msgid);
        result->addObject(str);
    }
    
    return result;
}

static clist * msg_id_from_string_array(Array * msgids)
{
    clist * result;
    
    if (msgids == NULL)
        return NULL;
    
    if (msgids->count() == 0)
        return NULL;
    
    result = clist_new();
    for(unsigned int i = 0 ; i < msgids->count() ; i ++) {
        String * msgid = (String *) msgids->objectAtIndex(i);
        clist_append(result, strdup(msgid->UTF8Characters()));
    }
    
    return result;
}

struct mailimf_fields * MessageHeader::createIMFFieldsAndFilterBcc(bool filterBcc)
{
    struct mailimf_date_time * imfDate;
    char * imfMsgid;
    char * imfSubject;
    struct mailimf_mailbox_list * imfFrom;
    struct mailimf_address_list * imfReplyTo;
    struct mailimf_address_list * imfTo;
    struct mailimf_address_list * imfCc;
    struct mailimf_address_list * imfBcc;
    clist * imfInReplyTo;
    clist * imfReferences;
    struct mailimf_fields * fields;
    
    imfDate = NULL;
    if (date() != (time_t) -1) {
        //MCLog("%lu", date());
        imfDate = dateFromTimestamp(date());
    }
    imfFrom = NULL;
    if (from() != NULL) {
        imfFrom = lep_mailbox_list_from_array(Array::arrayWithObject(from()));
    }
    imfReplyTo = lep_address_list_from_array(replyTo());
    imfTo = lep_address_list_from_array(to());
    imfCc = lep_address_list_from_array(cc());
    imfBcc = NULL;
    if (!filterBcc) {
        imfBcc = lep_address_list_from_array(bcc());
    }
    imfMsgid = NULL;
    if (messageID() != NULL) {
        imfMsgid = strdup(messageID()->UTF8Characters());
    }
    imfInReplyTo = msg_id_from_string_array(inReplyTo());
    imfReferences = msg_id_from_string_array(references());
    imfSubject = NULL;
    if ((subject() != NULL) && (subject()->length() > 0)) {
        Data * data;
        
        data = subject()->encodedMIMEHeaderValueForSubject();
        if (data->bytes() != NULL) {
            imfSubject = strdup(data->bytes());
        }
    }

    fields = mailimf_fields_new_with_data_all(imfDate,
        imfFrom,
        NULL /* sender */,
        imfReplyTo,
        imfTo,
        imfCc,
        imfBcc,
        imfMsgid,
        imfInReplyTo,
        imfReferences,
        imfSubject);
    
    if (mExtraHeaders != NULL) {
        mc_foreachhashmapKeyAndValue(String, header, String, value, mExtraHeaders) {
            struct mailimf_field * field;
            
            field = mailimf_field_new_custom(strdup(header->UTF8Characters()), strdup(value->UTF8Characters()));
            mailimf_fields_add(fields, field);
        }
    }
    
    return fields;
}

extern "C" {
    extern int mailimap_hack_date_time_parse(char * str,
        struct mailimap_date_time ** result,
        size_t progr_rate,
        progress_function * progr_fun);
}

#pragma mark IMAP mailbox conversion

static Array * imap_mailbox_list_to_address_array(clist * imap_mailbox_list)
{
    clistiter * cur;
    Array * result;
    
    result = Array::array();
    
    for(cur = clist_begin(imap_mailbox_list) ; cur != NULL ;
        cur = clist_next(cur)) {
        struct mailimap_address * imap_addr;
        Address * address;
        
        imap_addr = (struct mailimap_address *) clist_content(cur);
        address = Address::addressWithIMAPAddress(imap_addr);
        result->addObject(address);
    }
    
    return result;
}

void MessageHeader::importIMAPEnvelope(struct mailimap_envelope * env)
{
    if (env->env_date != NULL) {
        size_t cur_token;
        struct mailimf_date_time * date_time;
        int r;

        cur_token = 0;
        r = mailimf_date_time_parse(env->env_date, strlen(env->env_date),
            &cur_token, &date_time);
        if (r == MAILIMF_NO_ERROR) {
            time_t timestamp;

            // date
            timestamp = timestampFromDate(date_time);
            setDate(timestamp);
            setReceivedDate(timestamp);
            mailimf_date_time_free(date_time);
        }
        else {
            struct mailimap_date_time * imap_date;

            r = mailimap_hack_date_time_parse(env->env_date, &imap_date, 0, NULL);
            if (r == MAILIMAP_NO_ERROR) {
                time_t timestamp;

                timestamp = timestampFromIMAPDate(imap_date);
                setDate(timestamp);
                setReceivedDate(timestamp);
                mailimap_date_time_free(imap_date);
            }
        }
    }

    if (env->env_subject != NULL) {
        char * subject;

        // subject
        subject = env->env_subject;
        setSubject(String::stringByDecodingMIMEHeaderValue(subject));
    }

    if (env->env_sender != NULL) {
        if (env->env_sender->snd_list != NULL) {
            Array * addresses;

            addresses = imap_mailbox_list_to_address_array(env->env_sender->snd_list);
            if (addresses->count() > 0) {
                setSender((Address *) addresses->objectAtIndex(0));
            }
        }
    }

    if (env->env_from != NULL) {
        if (env->env_from->frm_list != NULL) {
            Array * addresses;

            addresses = imap_mailbox_list_to_address_array(env->env_from->frm_list);
            if (addresses->count() > 0) {
                setFrom((Address *) addresses->objectAtIndex(0));
            }
        }
    }

        // skip Sender header

    if (env->env_reply_to != NULL) {
        if (env->env_reply_to->rt_list != NULL) {
            Array * addresses;

            addresses = imap_mailbox_list_to_address_array(env->env_reply_to->rt_list);
            setReplyTo(addresses);
        }
    }

    if (env->env_to != NULL) {
        if (env->env_to->to_list != NULL) {
            Array * addresses;

            addresses = imap_mailbox_list_to_address_array(env->env_to->to_list);
            setTo(addresses);
        }
    }

    if (env->env_cc != NULL) {
        if (env->env_cc->cc_list != NULL) {
            Array * addresses;

            addresses = imap_mailbox_list_to_address_array(env->env_cc->cc_list);
            setCc(addresses);
        }
    }

    if (env->env_bcc != NULL) {
        if (env->env_bcc->bcc_list != NULL) {
            Array * addresses;

            addresses = imap_mailbox_list_to_address_array(env->env_bcc->bcc_list);
            setBcc(addresses);
        }
    }

    if (env->env_in_reply_to != NULL) {
        size_t cur_token;
        clist * msg_id_list;
        int r;

        cur_token = 0;
        r = mailimf_msg_id_list_parse(env->env_in_reply_to,
            strlen(env->env_in_reply_to), &cur_token, &msg_id_list);
        if (r == MAILIMF_NO_ERROR) {
            Array * msgids;

            msgids = msg_id_to_string_array(msg_id_list);
            setInReplyTo(msgids);
            // in-reply-to
            clist_foreach(msg_id_list, (clist_func) mailimf_msg_id_free, NULL);
            clist_free(msg_id_list);
        }
    }

    if (env->env_message_id != NULL) {
        char * msgid;
        size_t cur_token;
        int r;

        cur_token = 0;
        r = mailimf_msg_id_parse(env->env_message_id, strlen(env->env_message_id),
            &cur_token, &msgid);
        if (r == MAILIMF_NO_ERROR) {
            // msg id
            String * str;

            str = String::stringWithUTF8Characters(msgid);
            setMessageID(str);
            mailimf_msg_id_free(msgid);
        }
    }
}

void MessageHeader::importIMAPReferences(Data * data)
{
    size_t cur_token;
    struct mailimf_fields * fields;
    int r;
    struct mailimf_single_fields single_fields;
    
    cur_token = 0;
    r = mailimf_fields_parse(data->bytes(), data->length(), &cur_token, &fields);
    if (r != MAILIMF_NO_ERROR) {
        return;
    }
    
    mailimf_single_fields_init(&single_fields, fields);
    if (single_fields.fld_references != NULL) {
        Array * msgids;
        
        msgids = msg_id_to_string_array(single_fields.fld_references->mid_list);
        setReferences(msgids);
    }
    if (single_fields.fld_subject != NULL) {
        if (single_fields.fld_subject->sbj_value != NULL) {
            bool broken;
            char * value;
            bool isASCII;
            
            broken = false;
            value = single_fields.fld_subject->sbj_value;
            
            isASCII = true;
            for(char * p = value ; * p != 0 ; p ++) {
                if ((unsigned char) * p >= 128) {
                    isASCII = false;
                }
            }
            if (strstr(value, "windows-1251") == NULL) {
                if (isASCII) {
                    broken = true;
                }
            }
            
            //MCLog("charset: %s %s", value, MCUTF8(charset));
            
            if (!broken) {
                setSubject(String::stringByDecodingMIMEHeaderValue(single_fields.fld_subject->sbj_value));
            }
        }
    }
    
    mailimf_fields_free(fields);
}

void MessageHeader::importIMAPInternalDate(struct mailimap_date_time * date)
{
    setReceivedDate(timestampFromIMAPDate(date));
}

Array * MessageHeader::recipientWithReplyAll(bool replyAll, bool includeTo, bool includeCc, Array * senderEmails)
{
    bool hasCc;
    bool hasTo;
    Set * addedAddresses;
    Array * toField;
    Array * ccField;
    bool containsSender;
    Array * senderEmailsMailboxes;
    
    toField = NULL;
    ccField = NULL;
    
    hasTo = false;
    hasCc = false;
    addedAddresses = new Set();
    senderEmailsMailboxes = Array::array();

    containsSender = false;
    if (senderEmails != NULL) {
        mc_foreacharray(Address, address, senderEmails) {
            senderEmailsMailboxes->addObject(address->mailbox()->lowercaseString());
        }
        if (from() != NULL) {
            if (senderEmailsMailboxes->containsObject(from()->mailbox()->lowercaseString())) {
                containsSender = true;
            }
        }
        if (sender() != NULL) {
            if (senderEmailsMailboxes->containsObject(sender()->mailbox()->lowercaseString())) {
                containsSender = true;
            }
        }
    }
    
    if (containsSender) {
        Array * recipient;
        
        recipient = new Array();
        if (to() != NULL) {
            for(unsigned int i = 0 ; i < to()->count() ; i ++) {
                Address * address = (Address *) to()->objectAtIndex(i);
                if (addedAddresses->containsObject(address->mailbox()->lowercaseString())) {
                    continue;
                }
                if ((from() != NULL) && address->mailbox()->isEqualCaseInsensitive(from()->mailbox())) {
                    recipient->addObjectsFromArray(replyTo());
                    if (replyTo() != NULL) {
                        for(unsigned int j = 0 ; j < replyTo()->count() ; j ++) {
                            Address * rtAddress = (Address *) replyTo()->objectAtIndex(j);
                            if (addedAddresses->containsObject(rtAddress->mailbox()->lowercaseString())) {
                                continue;
                            }
                            addedAddresses->addObject(rtAddress->mailbox()->lowercaseString());
                        }
                    }
                }
                else {
                    if (address->mailbox() != NULL) {
                        recipient->addObject(address);
                        addedAddresses->addObject(address->mailbox()->lowercaseString());
                    }
                }
                hasTo = true;
            }
        }
        toField = recipient;
        toField->retain()->autorelease();
        recipient->release();
        
        if (replyAll) {
            recipient = new Array();
             if (cc() != NULL) {
               for(unsigned int i = 0 ; i < cc()->count() ; i ++) {
                    Address * address = (Address *) cc()->objectAtIndex(i);
                    if (addedAddresses->containsObject(address->mailbox()->lowercaseString())) {
                        continue;
                    }
                    if (address->mailbox() == NULL)
                        continue;
                    recipient->addObject(address);
                    addedAddresses->addObject(address->mailbox()->lowercaseString());
                    hasCc = true;
                }
            }
            ccField = recipient;
            ccField->retain()->autorelease();
            recipient->release();
        }
        
        if (!hasTo && !hasCc) {
            hasTo = true;
            toField = Array::arrayWithObject(from());
        }
    }
    else {
        addedAddresses->addObjectsFromArray(senderEmailsMailboxes);
        
        if (replyTo() != NULL && replyTo()->count() > 0) {
            hasTo = true;
            toField = replyTo();
            for(unsigned int i = 0 ; i < replyTo()->count() ; i ++) {
                Address * address = (Address *) replyTo()->objectAtIndex(i);
                if (address->mailbox() == NULL)
                    continue;
                addedAddresses->addObject(address->mailbox()->lowercaseString());
            }
        }
        else {
            if (from() != NULL && from()->mailbox() != NULL) {
                hasTo = true;
                toField = Array::arrayWithObject(from());
                addedAddresses->addObject(from()->mailbox()->lowercaseString());
            }
        }
        
        if (replyAll) {
            Array * recipient;
            
            recipient = new Array();
            if (to() != NULL) {
                for(unsigned int i = 0 ; i < to()->count() ; i ++) {
                    Address * address = (Address *) to()->objectAtIndex(i);
                    if (addedAddresses->containsObject(address->mailbox()->lowercaseString())) {
                        continue;
                    }
                    if (address->mailbox() == NULL)
                        continue;
                    recipient->addObject(address);
                    addedAddresses->addObject(address->mailbox()->lowercaseString());
                }
            }
            if (cc() != NULL) {
                for(unsigned int i = 0 ; i < cc()->count() ; i ++) {
                    Address * address = (Address *) cc()->objectAtIndex(i);
                    if (addedAddresses->containsObject(address->mailbox()->lowercaseString())) {
                        continue;
                    }
                    if (address->mailbox() == NULL)
                        continue;
                    recipient->addObject(address);
                    addedAddresses->addObject(address->mailbox()->lowercaseString());
                }
            }
            if (recipient->count() > 0) {
                hasCc = true;
            }
            ccField = recipient;
            ccField->retain()->autorelease();
            recipient->release();
        }
    }
    
    addedAddresses->release();
    
    Array * result;
    result = Array::array();
    if (hasTo && includeTo)
        result->addObjectsFromArray(toField);
    if (hasCc && includeCc)
        result->addObjectsFromArray(ccField);
    
    return result;
}

MessageHeader * MessageHeader::replyHeader(bool replyAll, Array * addressesExcludedFromRecipient)
{
    MessageHeader * result;
    String * subjectValue;
    Array * referencesValue;
    Array * inReplyTo;
    Array * toValue;
    Array * ccValue;
    
    referencesValue = NULL;
    inReplyTo = NULL;
    
    result = new MessageHeader();
    if (subject() == NULL) {
        subjectValue = MCSTR("Re: ");
    }
    else {
        if (!subject()->lowercaseString()->hasPrefix(MCSTR("re:"))) {
            subjectValue = MCSTR("Re: ")->stringByAppendingString(subject());
        }
        else {
            subjectValue = (String *) subject()->copy()->autorelease();
        }
    }
    if (references() != NULL) {
        referencesValue = (Array *) (references()->copy());
        referencesValue->autorelease();
        if (messageID() != NULL ) {
            referencesValue->addObject(messageID());
        }
    }
    if (messageID()) {
        inReplyTo = Array::array();
        inReplyTo->addObject(messageID());
    }
    toValue = recipientWithReplyAll(replyAll, true, false, addressesExcludedFromRecipient);
    ccValue = recipientWithReplyAll(replyAll, false, true, addressesExcludedFromRecipient);;
    
    result->setSubject(subjectValue);
    result->setReferences(referencesValue);
    result->setInReplyTo(inReplyTo);
    result->setTo(toValue);
    result->setCc(ccValue);
    
    result->autorelease();
    return result;
}

MessageHeader * MessageHeader::forwardHeader()
{
    MessageHeader * result;
    String * subjectValue;
    Array * referencesValue;
    Array * inReplyTo;
    
    referencesValue = NULL;
    inReplyTo = NULL;

    result = new MessageHeader();
    if (subject() == NULL) {
        subjectValue = MCSTR("Fw: ");
    }
    else {
        subjectValue = MCSTR("Fw: ")->stringByAppendingString(subject()->extractedSubjectAndKeepBracket(true));
    }
    if (references() != NULL) {
        referencesValue = (Array *) (references()->copy());
        referencesValue->autorelease();
        if (messageID() != NULL ) {
            referencesValue->addObject(messageID());
        }
    }
    if (messageID() != NULL) {
        inReplyTo = Array::array();
        inReplyTo->addObject(messageID());
    }
    result->setSubject(subjectValue);
    result->setReferences(referencesValue);
    result->setInReplyTo(inReplyTo);
    
    result->autorelease();
    return result;
}

HashMap * MessageHeader::serializable()
{
    HashMap * result = Object::serializable();
    
    if (messageID() != NULL) {
        result->setObjectForKey(MCSTR("messageID"), messageID());
    }
    if (mMessageIDAutoGenerated) {
        result->setObjectForKey(MCSTR("messageIDAutoGenerated"), Value::valueWithBoolValue(true));
    }
    if (references() != NULL) {
        result->setObjectForKey(MCSTR("references"), references());
    }
    if (inReplyTo() != NULL) {
        result->setObjectForKey(MCSTR("inReplyTo"), inReplyTo());
    }
    if (sender() != NULL) {
        result->setObjectForKey(MCSTR("sender"), sender()->serializable());
    }
    if (from() != NULL) {
        result->setObjectForKey(MCSTR("from"), from()->serializable());
    }
    if (to() != NULL) {
        result->setObjectForKey(MCSTR("to"), to()->serializable());
    }
    if (cc() != NULL) {
        result->setObjectForKey(MCSTR("cc"), cc()->serializable());
    }
    if (bcc() != NULL) {
        result->setObjectForKey(MCSTR("bcc"), bcc()->serializable());
    }
    if (replyTo() != NULL) {
        result->setObjectForKey(MCSTR("replyTo"), replyTo()->serializable());
    }
    if (subject() != NULL) {
        result->setObjectForKey(MCSTR("subject"), subject());
    }
    result->setObjectForKey(MCSTR("date"), String::stringWithUTF8Format("%lld", (unsigned long long) date()));
    result->setObjectForKey(MCSTR("receivedDate"), String::stringWithUTF8Format("%lld", (unsigned long long) receivedDate()));
    if (mExtraHeaders != NULL) {
        result->setObjectForKey(MCSTR("extraHeaders"), mExtraHeaders);
    }
    
    return result;
}

void MessageHeader::importSerializable(HashMap * hashmap)
{
    setMessageID((String *) hashmap->objectForKey(MCSTR("messageID")));
    
    Value * value = (Value *)hashmap->objectForKey(MCSTR("messageIDAutoGenerated"));
    mMessageIDAutoGenerated = value != NULL && value->boolValue();
    
    setReferences((Array *) hashmap->objectForKey(MCSTR("references")));
    setInReplyTo((Array *) hashmap->objectForKey(MCSTR("inReplyTo")));
    setSender((Address *) Object::objectWithSerializable((HashMap *) hashmap->objectForKey(MCSTR("sender"))));
    setFrom((Address *) Object::objectWithSerializable((HashMap *) hashmap->objectForKey(MCSTR("from"))));
    setTo((Array *) Object::objectWithSerializable((HashMap *) hashmap->objectForKey(MCSTR("to"))));
    setCc((Array *)Object::objectWithSerializable((HashMap *) hashmap->objectForKey(MCSTR("cc"))));
    setBcc((Array *)Object::objectWithSerializable((HashMap *) hashmap->objectForKey(MCSTR("bcc"))));
    setReplyTo((Array *)Object::objectWithSerializable((HashMap *) hashmap->objectForKey(MCSTR("replyTo"))));
    setSubject((String *) hashmap->objectForKey(MCSTR("subject")));
    setDate((time_t) ((String *) hashmap->objectForKey(MCSTR("date")))->unsignedLongLongValue());
    setReceivedDate((time_t) ((String *) hashmap->objectForKey(MCSTR("receivedDate")))->unsignedLongLongValue());
    setExtraHeaders((HashMap *) hashmap->objectForKey(MCSTR("extraHeaders")));
}

static void * createObject()
{
    return new MessageHeader();
}

INITIALIZE(MessageHeader)
{
    Object::registerObjectConstructor("mailcore::MessageHeader", &createObject);
}
